<?php
/**
 * Open接口请求检测
 * User: dxk <1024114660@qq.com>
 * Date: 2022/4/7
 */
namespace App\Service\OpenApi\AdminApi;

use App\Service\Core\Admin\Power\AdminUserPowerService;
use App\Service\Core\Admin\User\AdminUserService;
use App\Service\Core\Sys\Config\SysConfigService;
use App\Service\OpenApi\ApiLogService;

class ApiRequestCheckService
{
    public int $operate_id = 0;
    public int $permissions = 0;
    public string $operate_by = '';
    public array $login_params = [];
    public string $app_key = '';
    public string $app_secret;
    public string $app_version = '';
    // 请求接口的token
    public string $token = "";
    private string $method = '';
    private bool $has_check_appauth = false;
    public string|null $service_class = null;
    public string $a = '';
    //必须的系统级别的参数
    private array $need_sys_params = ['method', 'app_key', 'token', 'timestamp', 'timeout', 'noncestr', 'sign', 'app_version', 'param_json'];
    public array $request = [];
    //忽略token认证的接口
    private array $ignore_token_method = [
        'passport.login.index',
        'passport.login.sendCode',
        'passport.login.resetPassword',
        'common.common.uploadImage',
    ];

    /**
     * 接口验证
     * @param array $request 业务参数
     * @return bool|array
     */
    public function checkParam(array $request): bool|array
    {
        global $IS_API;
        $IS_API = 1;
        $debugbar = debugbar();
        $debugbar->disable();
        $this->request = $request;
        //检测应用是否可用 --- 批量调用接口只用再次检测签名和接口权限即可
        if ($this->has_check_appauth === false) {
            $check_res = $this->checkApp();
            if ($check_res !== true) {
                return $check_res;
            }
        }
        //检测输入参数.
        $check_res = $this->checkSysParams();
        if ($check_res !== true) {
            return $check_res;
        }
        //检测签名
        $check_res = $this->checkSign();
        if ($check_res !== true) {
            return $check_res;
        }
        //业务参数处理
        $params = json_decode($this->request['param_json'], true);
        $this->request = array_merge($this->request, $params);
        //检测Token
        $check_res = $this->checkToken();
        if ($check_res !== true) {
            return $check_res;
        }
        $this->has_check_appauth = true;
        return true;
    }

    /**
     * 检测系统参数是否传递(错误码10000系列)
     * @return array|bool
     */
    private function checkSysParams(): bool|array
    {
        if (empty($this->request)) {
            return alert_info(10001, '空请求', ['json' => $this->request, 'post' => $_POST, 'get' => $_GET]);
        }
        foreach ($this->need_sys_params as $param) {
            if (!array_key_exists($param, $this->request)) {
                return alert_info(10002, '参数' . $param . '未传递');
            }
            if (empty($this->request[$param]) && $param != 'token') {
                return alert_info(10003, '参数' . $param . '错误');
            }
        }
        //检测接口是否存在
        $this->method = $this->request['method'];
        $method_arr = explode('.', $this->method);
        if (count($method_arr) != 3) {
            return alert_info(10004, 'method参数错误02');
        }
        $m = ucfirst($method_arr[0]);
        $c = ucfirst($method_arr[1]);
        $a = \Str::camel($method_arr[2]);
        if (empty($m) || empty($c) || empty($a)) {
            return alert_info(10005, 'method参数错误03');
        }
        $c = $c . 'Api';
        $service_file = app_path('OpenApi' . DIRECTORY_SEPARATOR . 'AdminApi' . DIRECTORY_SEPARATOR . $m . DIRECTORY_SEPARATOR . $c) . '.php';
        if (!file_exists($service_file)) {
            return alert_info(10006, '接口不存在' . $this->method);
        }
        $class = 'App\\OpenApi\\AdminApi\\' . $m . '\\' . $c;
        if (!class_exists($class)) {
            return alert_info(10007, '接口不存在' . $this->method);
        }
        if (!is_callable([$class, $a], true)) {
            return alert_info(10008, '接口不存在' . $this->method);
        }
        $this->service_class = $class;
        $this->a = $a;
        $this->token = trim($this->request['token']);
        //检测随机串
        $noncestr = $this->request['noncestr'];
        if (empty($noncestr) || strlen($noncestr) != 32) {
            return alert_info(10009, 'noncestr参数错误');
        }
        //检测时效
        $timestamp = intval($this->request['timestamp']);
        $timeout = trim($this->request['timeout']);
        if (!is_numeric($timeout) || $timeout <= 0) {
            return alert_info(10010, 'timeout参数错误');
        }
        if (strlen($timestamp) > 10) {
            $timestamp = substr($timestamp, 0, 10);
        }
        $current_time = time();
        if ($current_time > ($timestamp + $timeout) && !is_local() && !config('sys.is_pts')) {
            return alert_info(10011, '请求链接已失效，如果是正常访问请校对时间。 timestamp：' . $timestamp . ' timeout:' . $timeout . ' 当前时间:' . $current_time);
        }
        //app版本控制
        $this->app_version = trim($this->request['app_version']);
        if (!empty($this->app_version)) {
            $ignore_method = [...$this->ignore_token_method];
            if (!in_array($this->method, $ignore_method)) {
                $min_version = SysConfigService::getInstance()->getConfig('mini_app.min_version', 0);
                if (version_compare($this->app_version, $min_version, '<')) {
                    $latest_version = SysConfigService::getInstance()->getConfig('mini_app.latest_version');
                    return alert_info(10012, '您的APP版本过低，请升级到' . $min_version . '或以上版本', [
                        'latest_version' => $latest_version,
                    ]);
                }
            }
        }
        return true;
    }

    /**
     * 验证应用是否可用（错误码20000系列）
     * @return array|bool
     */
    private function checkApp(): bool|array
    {
        $this->app_key = $this->request['app_key'] ?? '';
        $app_secret = config('openapi.' . $this->app_key);
        if (empty($app_secret)) {
            return alert_info(20001, '不存在的应用');
        }
        $this->app_secret = $app_secret;
        return true;
    }

    /**
     * 验证access_token（错误码40000系列）
     * @return array|bool
     */
    private function checkToken(): bool|array
    {
        $need_login = true;
        if (in_array($this->method, $this->ignore_token_method)) {
            $need_login = false;
        }
        if (empty($this->token)) {
            if ($need_login) {
                return alert_info(40001, '用户未登录');
            } else {
                return true;
            }
        }
        $check_res = (new AppAuthService($this->app_key))->checkAuth($this->token);
        if ($check_res['code'] !== 0 && $need_login) {
            return alert_info(40002, $check_res['msg']);
        }
        $params = $check_res['data']['params'] ?? [];
        $token_data = $check_res['data'];
        $this->operate_id = (int)$token_data['operate_id'];
        $check_res = AdminUserService::getInstance()->checkUserUsable($this->operate_id);
        if ($check_res['code'] !== 0 && $need_login) {
            return alert_info(40003, $check_res['msg']);
        }
        $user_info = $check_res['data']['user_info'];
        $this->permissions = AdminUserPowerService::getInstance()->staffPermissions($user_info['user_id']);
        $this->operate_by = (string)$user_info['username'];
        $this->login_params = $params;
        return true;
    }

    /**
     * 签名验证(错误码60000系列)
     * @return array|bool
     */
    private function checkSign(): bool|array
    {
        $sign = trim($this->request['sign']);
        $params = easy_get_field($this->request, ['method', 'app_key', 'app_version', 'timestamp', 'timeout', 'noncestr', 'param_json', 'token']);
        unset($params['sign']);
        ksort($params);
        $stringToBeSigned = '';
        foreach ($params as $k => $v) {
            $stringToBeSigned .= "$k$v";
        }
        unset($k, $v);
        $check_sign = strtoupper(md5($this->app_secret . $stringToBeSigned . $this->app_secret));
        if (strcmp($check_sign, $sign) !== 0 && !config('sys.is_pts')) {
            return alert_info(60001, '签名错误', ['sign_str' => $stringToBeSigned, 'sign' => $check_sign]);
        }
        return true;
    }

    /**
     * 信息输出
     * @param int $code 错误码 0 代表成功，其他为错误
     * @param string $msg
     * @param array $data
     * @return false|string
     */
    public function jsonAlert(int $code, string $msg = '', array $data = []): bool|string
    {
        $msg = trim($msg);
        $data = easy_app_filter_var(obj_to_array($data));
        $result = ['code' => $code, 'msg' => $msg, 'data' => $data];
        //记录日志
        $log_data = [
            'app_key' => $this->app_key,
            'operate_id' => $this->operate_id,
            'operate_by' => $this->operate_by,
            'method' => $this->method,
            'token' => $this->token,
            'request' => $this->request,
        ];
        if ($code !== 20001) {
            (new ApiLogService())->log($log_data, $code, $msg, $data);
        }
        return json_encode($result, JSON_UNESCAPED_UNICODE);
    }

    /**
     * 信息输出
     * @param int $code 错误码 0 代表成功，其他为错误
     * @param string $msg
     * @param array $data
     * @param array $request
     * @return false|string
     */
    public function batchJsonAlert(int $code, string $msg = '', array $data = [], array $request = []): bool|string
    {
        $msg = trim($msg);
        $data = easy_app_filter_var(obj_to_array($data));
        $result = ['code' => $code, 'msg' => $msg, 'data' => $data];
        //记录日志
        $log_data = [
            'app_key' => $this->app_key,
            'operate_id' => $this->operate_id,
            'operate_by' => $this->operate_by,
            'method' => 'batch_api',
            'token' => $this->token,
            'request' => $request,
        ];
        if ($code !== 20001) {
            (new ApiLogService())->log($log_data, $code, $msg, $data);
        }
        return json_encode($result, JSON_UNESCAPED_UNICODE);
    }
}
